wireshark插件 解析自定义协议
Table of Contents
Wireshark内置了Lua脚本编写插件,无需配置额外的环境,使用起来吧比较便。
使用Lua编写Wireshark协议解析插件,有几个比较重要的概念:
- Dissector,解剖器,用来解析包的类,我们要编写的,也是一个Dissector。
- DissectorTable,解析器表是Wireshark中解析器的组织形式,是某一种协议的子解析器的一个列表,其作用是把所有的解析器组织成一种树状结构,便于Wireshark在解析包的时候自动选择对应的解析器。例如TCP协议的子解析器 http, smtp, sip等都被加在了"tcp.port"这个解析器表中,可以根据抓到的包的不同的tcp端口号,自动选择对应的解析器。
基础
-- create a new protocol
local proto_name = "PNASKCP"
local proto_desc = "Private NAS Protocol (KCP)"
local proto_obj = Proto(proto_name, proto_desc)
local proto_port = 5067
--register this dissector add Packet Details
DissectorTable.get("udp.port"):add(proto_port, proto_obj)
ProtoField
表示协议字段,一般用于解析字段后往解析树上添加节点。根据字段类型不同,其接口可以分为两大类。 这些接口都会返回一个新的字段对象。方括号内是可选字段,花括号内是可替换的类型字段。
ProtoField.{type}(abbr, [name], [base], [valuestring], [mask], [desc])
·type包括:uint8, uint16, uint24, uint32, uint64, framenum, float, double, string, stringz, bytes, bool, ipv4, ipv6, ether,oid, guid
参数
- abbr 字段的缩写名称(过滤器中使用的字符串)。
- name (optional) 字段的实际名称(出现在树中的字符串)。
- base (optional) base.DEC,base.HEX或base.OCT,base.DEC_HEX,base.HEX_DEC,base.UNIT_STRING或base.RANGE_STRING。
- valuestring (optional) 包含与值对应的文本的表,或包含与值 ({min, max, “string”}) 对应的范围字符串值表的表(如果基数为 )base.RANGE_STRING,或包含单位名称的表如果 base 是base.UNIT_STRING.
- mask (optional) 此字段的整数掩码。
- desc (optional) 字段说明。
fields.tlv_value_int16 = ProtoField.uint16(proto_name .. ".tlv_value_int", "PNAS TLV VALUE", base.HEX)
fields.uri_ipv4 = ProtoField.ipv4(proto_name .. ".uri_ipv4", "IP ADDRESS")
tvb
Tvb(Testy Virtual Buffer)表示报文缓存,也就是实际的报文数据,可以通过下面介绍的TvbRange从报文数据中解出信息。主要接口有:
-- tvb(offset, 4)表示从offset开始之后的4个字节
subtree:add_le(fields.peer_ipaddr, tvb(offset, 4))
pinfo
报文信息(packet information)。主要接口有:
-- show protocol name
pinfo.cols.protocol = "PNASKCP"
-- show in info
pinfo.cols.info:set("Private NAS Protocol (CALL_REQUEST)")
TreeItem
表示报文解析树中的一个树节点。主要接口有:
还有注意一下网络字节序的问题,如果是网络字节序需要用add_le添加节点
-- kcp header
local subtree = tree:add(proto_obj, tvb(offset, lmmh_len), "KCP")
--conv
subtree:add_le(fields.conv, tvb(offset, 4))
offset = offset + 4
--cmd
subtree:add_le(fields.cmd, tvb(offset, 1))
offset = offset + 1
--frg
subtree:add_le(fields.frg, tvb(offset, 1))
offset = offset + 1
--wnd
subtree:add_le(fields.wnd, tvb(offset, 2))
offset = offset + 2
脚本模板
创建编译 packet-demo.lua 文件
do
-- 创建一个新的协议: 协议名称为 fuzz_proto,在Packet Details窗格显示为 Fuzz Custom Protocol
fuzz_protocol = Proto("fuzz_proto", "Fuzz Custom Protocol")
-- 定义协议字段
--[[ 自定义字段
protocol_type(2) -- 自定义协议编号
base_protocol(1) -- 下层协议
connection_index(1) -- 收发状态
round_index(4) -- 本轮次序号
--]]
local f_protocol_type = ProtoField.uint16("fuzz_proto.protocol_type", "Custom Protocol ID", base.DEC)
local f_base_protocol = ProtoField.uint8("fuzz_proto.base_protocol", "Base Protocol", base.DEC)
local f_connection_index = ProtoField.uint8("fuzz_proto.connection_index", "Connection ID", base.DEC)
local f_round_index = ProtoField.uint32("fuzz_proto.round_index", "Round Index", base.DEC)
-- 将字段添加到协议中
fuzz_protocol.fields = { f_protocol_type, f_base_protocol, f_connection_index, f_round_index }
local data_dis = Dissector.get("data")
-- 定义协议的解析函数
function FUZZ_dissector(buffer, pinfo, tree)
local length = buffer:len()
-- 确保报文有足够的长度供解析
if length < 8 then
return false
end
--验证一下identifier这个字段是不是0x12,如果不是的话,认为不是我要解析的packet
local v_identifier = buffer(0, 1)
if (v_identifier:uint() ~= 0x12) then
return false
end
--取出其他字段的值
local v_length = buffer(1, 1)
v_length = tonumber(tostring(v_length),16)
local v_data = buffer(2,v_length)
-- 显示协议名称 --在主窗口的 Protocol 字段显示的名称为 XX_Protobuf
-- pinfo.cols.protocol:set("XX_Protobuf")
pinfo.cols.protocol = "fuzz_proto"
--现在知道是我的协议了,放心大胆添加Packet Details
-- 在树形结构中添加Headers
local subtree = tree:add(fuzz_protocol, buffer(0, 7), "Fuzz Header")
-- 解析协议字段并添加到树中
subtree:add(f_protocol_type, buffer(0, 2))
local d_base_protocol = buffer(2, 1):uint()
local d_base_protocol_map = {
[0] = "UnSet",
[1] = "Ethernet",
[2] = "IP",
[3] = "TCP",
[4] = "UDP",
[5] = "TLS",
[6] = "HTTP"
}
local d_base_protocol = d_base_protocol_map[d_base_protocol] or "Unknown"
subtree:add(f_base_protocol, buffer(2, 1)):append_text(" (" .. d_base_protocol .. ")")
local d_connection_index = (buffer(3, 1):uint() < 127) and "Send" or "Receive"
subtree:add(f_connection_index, buffer(3, 1)):append_text(" (" .. d_connection_index .. ")")
-- 本轮次序号, 取值为十六进制,需要将其转换为十进制
local d_round_index = string.format("%d", buffer(4, 4):uint())
subtree:add(f_round_index, buffer(4, 4)):append_text(" (" .. d_round_index .. ")")
-- 解析下层协议
local payload = buffer(8, length - 8)
local subtree_payload = tree:add(fuzz_protocol, payload, "Current Custom Protocol")
subtree_payload:add("Payload", payload):append_text(" (" .. payload:len() .. " bytes)")
end
function fuzz_protocol.dissector(buffer, pinfo, tree)
if FUZZ_dissector(buffer, pinfo, tree) then
-- valid Fuzz diagram
else
-- data这个dissector几乎是必不可少的;当发现不是我的协议时,就应该调用data
data_dis.call(buffer, pinfo, tree)
end
end
-- 注册 dissector 到对应的 DLT_USER3 (150)
-- local wtap_encap_table = DissectorTable.get("wtap_encap")
-- wtap_encap_table:add(150, fuzz_protocol)
local tcp_encap_table = DissectorTable.get("tcp.port")
--因为我们的自定义协议的接受端口是1080,所以这里只需要添加到"tcp.port"这个DissectorTable里即可。
tcp_encap_table:add(1080, fuzz_protocol)
end
配置lua脚本
如图, 在/usr/share/wireshark/init.lua文件中,添加:
enable_lua = true
dofile("/home/peter/.config/wireshark/packet-demo.lua")
然后重新启动Wireshark或者点击【分析】-【重新载入Lua插件】,就可以启用你自己的lua插件了。
新版本wireshark4.2 中的位置有所改变
如图 init.lua需要 放置在 /usr/lib/x86_64-linux-gnu/wireshark/plugins/init.lua 位置
并添加
dofile(DATA_DIR.."/packet-demo.lua")
高级一点的玩法
如果我们协议的PayLoad里封装的其实是以太网包,能不能让Wireshark在我们的插件执行完之后,继续按照以太网格式解析其他部分呢?
这里,我们重新构造一下需要继续解析的数据,然后获取出一个以太网解析器就继续做下去
local raw_data = buf(8, buf:len() - 8)
Dissector.get("eth_maybefcs"):call(raw_data:tvb(), pinfo, tree)
替换上面代码的-- 解析下层协议即可 需要在注意,
- 获取的解析器名称应该是 eth_maybefcs,因为DissectorTable里写的eth,但是提示找不到。 eth_maybefcs,意思是可能带有fcs的eth帧
- raw_data需要调用一下tvb()函数,不然会提示你这个是userdata,不能使用。tvb的全称应该是Testy Virtual Buffer,用来存储Packet buffer的,要处理必须先转成这个。
调用eth_maybefcs解析器的时候,这些解析器会给协议栏赋值,覆盖掉我们之前写的wfuzz protocol。为了区分,我们可以在上面的代码之后加上:
pkt.cols.protocol:append("-FuzzProtocol")
就是不管协议栏被改成了什么,都在后面加上-FuzzProtocol,这样ARP、ICMP等就会变成 ARP-Fuzz、ICMP-Fuzz了,一眼就可以跟那些普通的ARP和ICMP区分出来。